package models

import (
	"3rd/mongo"
	goerrors "github.com/go-errors/errors"
	"gopkg.in/mgo.v2"
	"gopkg.in/mgo.v2/bson"
	"time"
)

type Contract struct {
	ContractID string `bson:"_id" json:"contractID,omitempty"`

	Sn     string `bson:"sn" json:"sn"  valid:"Required"`
	Status int    `bson:"status" json:"status"` //online/offline

	Customer *Demander       `bson:"demander" json:"customer,omitempty" valid:"Required"`
	Station  *StationProfile `bson:"station" json:"station,omitempty"`

	UnitPrice    float64 `bson:"unitPrice" json:"unitPrice" valid:"Required"`
	WaterQuality string  `bson:"waterQuality" json:"waterQuality" valid:"Required"`

	Total       int   `bson:"total" json:"total" valid:"Required"`
	Remain      int   `bson:"remain" json:"remain"`
	LeftBottles int   `bson:"leftBottles" json:"leftBottles"`
	State       int   `bson:"state" json:"state"` // bind state
	Created     int64 `bson:"created" json:"created"`
}

func AddCustomer(contract *Contract) error {
	contract.Customer.Location.Type = "point"
	contract.Remain = contract.Total
	contract.ContractID = contract.Sn
	contract.Created = time.Now().Unix()
	// add customer and sn, if the record exists and owned by this station then unset sn field
	// and create a new one, if not exists create a new one
	return mongo.DO(db_contract, col_contract, func(c *mgo.Collection) (err error) {
		//query := bson.M{"sn": contract.Sn}
		var rst Contract
		if err := c.FindId(contract.Sn).One(&rst); err != nil {
			if err == mgo.ErrNotFound {
				// not found
				contract.State = BINDED
				err = c.Insert(contract)
				if err != nil {
					return goerrors.New(err)
				}
			} else {
				return goerrors.New(err)
			}
		} else {
			// exists
			//			if rst.Station.Qid != contract.Station.Qid {
			//				// dev had been binded by the other station
			//				return errors.New(AlredyBindedByOthers, "binded")
			//			} else {
			//				// dev had been binded to the other customer by myself
			//				_, err := c.UpdateId(rst.ContractID, bson.M{"$set": bson.M{"sn":""}})
			//				if err != nil {
			//					return err
			//				}
			//				return c.Insert(contract)
			//			}
			// HACK:
			//			if err := c.UpdateId(rst.ContractID, bson.M{"$set": bson.M{"sn": ""}}); err != nil {
			//				return err
			//			}
			contract.State = BINDED
			if err := c.UpdateId(rst.ContractID, contract); err != nil {
				return goerrors.New(err)
			}
		}
		return nil
	})
}

func AddDev(sn string) error {
	return mongo.DO(db_contract, col_contract, func(c *mgo.Collection) (err error) {
		//query := bson.M{"sn": sn}
		var rst Contract
		if err := c.FindId(sn).One(&rst); err != nil {
			if err == mgo.ErrNotFound {
				// not found
				rst.State = DEVSTUB
				rst.Sn = sn
				rst.ContractID = sn
				rst.Status = ONLINE
				if err := c.Insert(rst); err != nil {
					return goerrors.New(err)
				}
			} else {
				return goerrors.New(err)
			}
		} else {
			if rst.State == BINDED {
				if err := c.UpdateId(rst.ContractID, bson.M{"$set": bson.M{"status":ONLINE}}); err != nil {
					return goerrors.New(err)
				}
				// push online
				PushMsg2MasterAsync(rst.Station.Qid, map[string]interface{}{
					"type":    NOTIFY_DEVUP,
					"sn":      sn,
					"address": rst.Customer.Address,
					"company": rst.Customer.Company,
				})
			} else if rst.State == USERSTUB {
				if err := c.UpdateId(rst.ContractID, bson.M{"$set": bson.M{"state": BINDED, "status":ONLINE}}); err != nil {
					return goerrors.New(err)
				}
				// push bind successful
				//go push.PushMsg2Point(rst.Station.Qid, sn, map[string]interface{}{"type": NOTIFY_BIND})
				PushMsg2MasterAsync(rst.Station.Qid, map[string]interface{}{
					"type": NOTIFY_BIND,
					"sn":   sn,
				})
				Log.Alert("%s bind %s:%s successful", rst.Station.StationNO, sn, rst.Customer.Company)
			}
		}
		return nil
	})
}

/*
func ModifyContract(contract *Contract) error {
	//contract.Customer.Sn = contract.ContractID
	return mongo.DO(db_contract, col_contract, func(c *mgo.Collection) (err error) {
		selector := bson.M{"_id": contract.ContractID, "demander.qid": contract.Customer.Qid}
		return c.Update(selector, contract)
	})
}*/

func ModifyCustomer(contract *Contract) error {
	//contract.Customer.Sn = contract.ContractID
	return mongo.DO(db_contract, col_contract, func(c *mgo.Collection) (err error) {
		selector := bson.M{"_id": contract.ContractID, "demander.qid": contract.Customer.Qid}
		if err := c.Update(selector, bson.M{"$set": bson.M{
			"waterQuality":          contract.WaterQuality,
			"total":                 contract.Total,
			"remain":                contract.Remain,
			"demander.address":      contract.Customer.Address,
			"demander.company":      contract.Customer.Company,
			"demander.tolerance":    contract.Customer.Tolerance,
			"demander.unitDelivery": contract.Customer.UnitDelivery,
			"demander.linkman":      contract.Customer.Linkman,
			"demander.phoneNo":      contract.Customer.PhoneNo,
		}}); err != nil {
			return goerrors.New(err)
		}
		return nil
	})
}

func GetBindedContract(sn string, contract *Contract) error {
	return mongo.DO(db_contract, col_contract, func(c *mgo.Collection) error {
		query := bson.M{"sn": sn, "state": BINDED}
		if err := c.Find(query).One(contract); err != nil {
			return goerrors.New(err)
		}
		return nil
	})
}

func CustomersOfStation(qid, stationID string, contracts *[]Contract) error {
	return mongo.DO(db_contract, col_contract, func(c *mgo.Collection) (err error) {
		query := bson.M{
			"station._id": stationID,
			"station.qid": qid,
		}

		if err := c.Find(query).Select(bson.M{"station": 0}).All(contracts); err != nil {
			return goerrors.New(err)
		}
		return nil
	})
}

func GetCustomers(cursor int64, limit int, contracts *[]Contract) error {
	return mongo.DO(db_contract, col_contract, func(c *mgo.Collection) (err error) {
		query := bson.M{
			//"_id":  bson.M{"$lt": sn},
			"created": bson.M{"$lt": cursor},
		}

		if err := c.Find(query).Limit(limit).Sort("-created").All(contracts); err != nil {
			return goerrors.New(err)
		}
		return nil
	})
}

func Stat(info *[]bson.M) error {
	return mongo.DO(db_contract, col_contract, func(c *mgo.Collection) (err error) {
		pipe := c.Pipe([]bson.M{
			{"$group": bson.M{
			"_id": bson.M{"$add":[]interface{}{"$status", bson.M{"$multiply": []interface{}{"$state", 2}}}}, 
			"count": bson.M{"$sum": 1}},
			},
		})

		if err := pipe.All(info); err != nil {
			return goerrors.New(err)
		}
		return nil
	})
}

func DelCustomer(qid, stationID, contractID string) error {
	return mongo.DO(db_contract, col_contract, func(c *mgo.Collection) (err error) {
		query := bson.M{
			"_id":         contractID,
			"station.qid": qid,
			"station._id": stationID,
		}
		if err := c.Remove(query); err != nil {
			return goerrors.New(err)
		}
		return nil
	})
}

func IncStock(sn string, stock, leftBottles int) error {
	return mongo.DO(db_contract, col_contract, func(c *mgo.Collection) (err error) {
		if err := c.UpdateId(sn, bson.M{"$inc": bson.M{"remain": stock, "leftBottles": leftBottles}}); err != nil {
			return goerrors.New(err)
		}
		return nil
	})
}

func SetStatus(sn string, status int) error {
	return mongo.DO(db_contract, col_contract, func(c *mgo.Collection) (err error) {
		var contract Contract
		if err := c.Find(bson.M{"_id": sn, "state": BINDED}).One(&contract); err != nil {
			return goerrors.New(err)
		}
		var noticeType int
		if status == OFFLINE {
			noticeType = NOTIFY_DEVDOWN
			Log.Alert("%s %s offline", contract.Customer.Company, sn)
		} else if status == ONLINE {
			noticeType = NOTIFY_DEVUP
			Log.Alert("%s %s online", contract.Customer.Company, sn)
		}
		PushMsg2MasterAsync(contract.Station.Qid, map[string]interface{}{
			"type":    noticeType,
			"sn":      sn,
			"address": contract.Customer.Address,
			"company": contract.Customer.Company,
		})
		if err := c.UpdateId(sn, bson.M{"$set": bson.M{"status": status}}); err != nil {
			return goerrors.New(err)
		}
		return nil
	})
}

func ClaimSn(qid, sn string) error {
		return mongo.DO(db_contract, col_contract, func(c *mgo.Collection) (err error) {
			if err := c.UpdateId(sn, bson.M{"$set": bson.M{"demander.qid": qid}}); err != nil {
				return goerrors.New(err)
			}
			return nil
	})
}

func GetSettings(qid string, contract *Contract) error {
	return mongo.DO(db_contract, col_contract, func(c *mgo.Collection) (err error) {
		if err := c.Find(bson.M{"demander.qid": qid}).One(contract); err != nil {
			return goerrors.New(err)
		}
		return nil
	})
}

func UpdateSettings(qid string, contract *Contract) error {
		return mongo.DO(db_contract, col_contract, func(c *mgo.Collection) (err error) {
		if err := c.Update(bson.M{"demander.qid": qid}, bson.M{"$set": bson.M{
			"waterQuality":          contract.WaterQuality,
			"total":                 contract.Total,
			"remain":                contract.Remain,
			"demander.address":      contract.Customer.Address,
			"demander.company":      contract.Customer.Company,
			"demander.tolerance":    contract.Customer.Tolerance,
			"demander.unitDelivery": contract.Customer.UnitDelivery,
			"demander.linkman":      contract.Customer.Linkman,
			"demander.phoneNo":      contract.Customer.PhoneNo,
		}}); err != nil {
			return goerrors.New(err)
		}
		return nil
	})
}
